/******************************************************************************

    Copyright (C) 2020 ErisedMedia
    All Rights Reserved.

    This source code and any compilation or derivative thereof is the
    proprietary information of ErisedMedia and is
    confidential in nature.
    Under no circumstances is this software to be exposed to or placed under an
    Open Source License of any type without the expressed written permission of
    ErisedMedia.

******************************************************************************/

/******************************************************************************

    File:   ADPersonFilter.cc
    Brief:  ADPersonFilter实现.

******************************************************************************/

/******************************************************************************

    Rev     Date        Author      Comments
--------------------------------------------------------------------------------
    001     20200715    RunyuanYe        Original

--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.

*******************************************************************************/

/******************************************************************************
*
*   INCLUDE FILES
*
******************************************************************************/
#include "Head.h"
#include "Tools.h"
#include "Polygon.h"
#ifdef USE_BACKTRACE
#include <execinfo.h>
#endif

/******************************************************************************
*
*    LOCAL MACROS
*
******************************************************************************/

// 用于使能过滤广告框中的人像
#define POLYGON_FILTER_THR  0.8f

#define AD_THR				0.8f

/******************************************************************************
*
*    LOCAL TYPEDEFS
*
******************************************************************************/

typedef struct _Box
{
    float xmin;
    float ymin;
    float xmax;
    float ymax;
} Box_S;

typedef struct _BBox
{
	int frame_id;
	int label;
    int xmin;
    int ymin;
    int xmax;
    int ymax;
	float score;
} BBox_S;

/******************************************************************************
*
*    STATIC DATA
*
******************************************************************************/

static std::vector<Point_S> filterPolyon = { 
    {0.0f, 1.0f},
    {1.0f, 1.0f},
    {2.0f/3.0f, 1.0f/2.0f},
    {1.0f/3.0f, 1.0f/2.0f},
};

/******************************************************************************
*
*    EXPORTED DATA
*
******************************************************************************/


/******************************************************************************
*
*    EXPORTED FUNCTION
*
******************************************************************************/


/******************************************************************************
*
*    STATIC FUNCTION PROTOTYPES
*
******************************************************************************/


/******************************************************************************
*
*    FUNCTION IMPLEMENTATION
*
******************************************************************************/

#ifdef USE_BACKTRACE
static void backtrace_handler(int sig)
{

  void *array[10];

  size_t size;

  // get void*'s for all entries on the stack

  size = backtrace(array, 10);

  // print out all the frames to stder

  fprintf(stderr, "Error: signal %d:\n", sig);

  backtrace_symbols_fd(array, size, STDERR_FILENO);

  exit(1);
}
#endif

// 用于使能过滤广告框中的人像, 返回false表示为广告框的人像
static bool PolygonFilter(const Box_S& bbox)
{
    std::vector<Point_S> person;

    float width = bbox.xmax - bbox.xmin;
    float height = bbox.ymax - bbox.ymin;
    float area = width * height;
    if(area <= 0.0f)
    {
        return false;
    }

    person.push_back({bbox.xmin, bbox.ymin});
    person.push_back({bbox.xmin, bbox.ymax});
    person.push_back({bbox.xmax, bbox.ymax});
    person.push_back({bbox.xmax, bbox.ymin});

    float ia = PolygonIA(person, filterPolyon);
    float topAreaRatio = (area - ia) / area;
    // printf("topAreaRatio %f\n", topAreaRatio);
    if(topAreaRatio >= POLYGON_FILTER_THR)
    {
        return false;
    }
    return true;
}


static int ListImages(string const &listFileName, queue<string> &images, std::set<string> &image_name_set) {

    std::ifstream listFile(listFileName.c_str());
    string line;
    while (std::getline(listFile, line))
    {
		auto image_lines = StringSplit(line, ' ');
		if(image_lines.size() < 1)
		{
			continue;
		}
		auto image_name = image_lines[0];
		image_name_set.insert(image_name);
		images.push(line);
    }

    return 0;
}

static int read_bbox(std::ifstream& bboxFile, int bbox_count, std::map<int, std::vector<BBox_S>>& bboxes, int select_label, float score_thr)
{
	for(int j = 0; j < bbox_count; ++j)
	{
		BBox_S bbox;
		bboxFile.read((char*)&bbox, sizeof(BBox_S));
		if(bboxFile.gcount() < (int)(sizeof(BBox_S)))
		{
			return -1;
		}

		if(bbox.label != select_label)
		{
			continue;
		}

		if(bbox.score < score_thr)
		{
			continue;
		}

		bboxes[bbox.frame_id].push_back(bbox);
	}
	return 0;
}


static int LoadBBox(string basePath, int split_count, std::set<string> const &image_name_set, std::map<string, std::map<int, std::vector<BBox_S>>>& bbox_map, int select_label, float score_thr)
{
	char listName[64];
	char bboxName[64];
	for(int i = 0; i < split_count; ++i)
	{
		sprintf(listName, "video_list%d_bbox_count.txt", i);
		sprintf(bboxName, "video_list%d_bbox.dat", i);
		string listFileName;
		string bboxFileName;

		listFileName = JoinPathEx(basePath, listName);
		bboxFileName = JoinPathEx(basePath, bboxName);

		std::ifstream listFile(listFileName.c_str());
		std::ifstream bboxFile(bboxFileName.c_str(), std::ios::binary);

		string line;
		long long bbox_count_offset = 0;
		while (std::getline(listFile, line))
		{
			auto image_lines = StringSplit(line, ',');
			if(image_lines.size() < 2)
			{
				continue;
			}
			auto image_name = image_lines[0];
			int bbox_count = 0;
			if(sscanf(image_lines[1].c_str(), "%d", &bbox_count) < 1)
			{
				continue;
			}
			
			if(image_name_set.find(image_name) == image_name_set.end())
			{
				bbox_count_offset += bbox_count;
				continue;
			}

			bboxFile.seekg(bbox_count_offset*sizeof(BBox_S), std::ios::beg);
			bbox_count_offset += bbox_count;

			auto fr_bb_map = bbox_map.find(image_name);
			if(fr_bb_map == bbox_map.end())
			{
				std::map<int, std::vector<BBox_S>> bboxes;
				read_bbox(bboxFile, bbox_count, bboxes, select_label, score_thr);
				bbox_map[image_name] = bboxes;
			}
			else
			{
				std::map<int, std::vector<BBox_S>>& bboxes = fr_bb_map->second;
				read_bbox(bboxFile, bbox_count, bboxes, select_label, score_thr);
			}
		}
	}

	return 0;
}


string baseImagePath = "/media/kasim/DataSet/Record";
string imageListfile = "/media/kasim/Data/data/Record/filter_low_select_file_list.txt";
string outPath = "/media/kasim/Data/data/Record";
int split_count = 8;
int select_label = 0;
float score_thr = 0.3f;

int main(int argc, char** argv)
{
    #ifdef USE_BACKTRACE
    signal(SIGSEGV, backtrace_handler);
    signal(SIGBUS, backtrace_handler);
    #endif
	int threadnum = 12;
    std::cout << "Usage: " << argv[0] << "baseImagePath imageListfile outPath threadnum" << std::endl;

    if (argc>1)
        baseImagePath = argv[1];
    if (argc>2)
        imageListfile = argv[2];
    if (argc>3)
        outPath = argv[3];
    if (argc>4)
        threadnum = atoi(argv[4]);

    std::cout << argv[0] << " " << baseImagePath << " " << imageListfile << " " << outPath << " " << threadnum << std::endl;

    std::queue<string> images;
	std::mutex         imagesLock;
	std::set<string>   image_name_set;

    std::queue<string> out_images;
	std::mutex         out_imagesLock;

	std::mutex         opencvLock;

    /* Load all image names.*/
    ListImages(imageListfile, images, image_name_set);
	int fileCount = images.size();
    if (fileCount == 0) {
        cerr << "\nError: No images existing under " << baseImagePath << endl;
        return -1;
    }

	std::map<string, std::map<int, std::vector<BBox_S>>> bbox_map;
	LoadBBox(baseImagePath, split_count, image_name_set, bbox_map, select_label, score_thr);

	if(threadnum<1)
	{
		threadnum = 1;
	}

    std::string outFileName;
	std::ofstream* outfile = nullptr;

	outFileName = JoinPathEx(outPath, "ad_" + GetFileName(imageListfile));
	outfile = new std::ofstream(outFileName, std::ios::binary);
	if(!outfile->is_open()) {
		cerr << "\nError: Open File " << outFileName << endl;
		return -1;
	}

	auto start_time = GetClockTicks();
	{
		std::thread workers[threadnum];
		for (int i = 0; i < threadnum; i++) {
			workers[i] = thread([&,i]() {
				cv::VideoCapture cap;
				while (true)
				{
					imagesLock.lock();
					if( images.empty() )
					{
						imagesLock.unlock();
						break;
					}
					else
					{
						// Get an context from queue
						auto image_line = images.front();
						images.pop();
						imagesLock.unlock();

						auto image_lines = StringSplit(image_line, ' ');
						if(image_lines.size() < 1)
						{
							continue;
						}
						auto image_name = image_lines[0];

						auto bboxes = bbox_map.find(image_name);
						if(bboxes == bbox_map.end())
						{
							continue;
						}

						auto image_path = JoinPathEx(baseImagePath, image_name);

						float width = 0.0f, height = 0.0f;
						opencvLock.lock();
						try
						{
							cap.open(image_path.c_str());
							if(!cap.isOpened())
							{
								std::cout << image_path << " open failed!" << std::endl;
								opencvLock.unlock();
								continue;
							}
							
							width = cap.get(CAP_PROP_FRAME_WIDTH);
							height = cap.get(CAP_PROP_FRAME_HEIGHT);
							cap.release();
							opencvLock.unlock();
						}
						catch(const std::exception& e)
						{
							std::cerr << e.what() << '\n';
							opencvLock.unlock();
							continue;
						}

						if(width < 100.0f || height < 100.0f)
						{
							continue;
						}
						
						auto& _bboxes = bboxes->second;
						int count = _bboxes.size();
						int filter_count = 0; // 广告人像帧数
						for(auto it : _bboxes)
						{
							auto& bbs = it.second;
							Box_S bbox;
							for(auto& bb : bbs)
							{
								bbox.xmin = (float)(bb.xmin) / width;
								bbox.xmax = (float)(bb.xmax) / width;
								bbox.ymin = (float)(bb.ymin) / height;
								bbox.ymax = (float)(bb.ymax) / height;
								if(!PolygonFilter(bbox))
								{
									filter_count++;
									break;
								}
							}
						}

						if((int)(count*AD_THR) < filter_count)
						{// 此为广告框人像，跳过
							continue;
						}

						out_imagesLock.lock();
						out_images.push(image_line);
						out_imagesLock.unlock();
					}
				}
			});
		}

		volatile bool break_out_worker = false;
		std::thread out_worker = thread([&]() {
			int file_count = 0;
			while (true)
			{
				bool is_empty = true;
				if(break_out_worker)
				{
					out_imagesLock.lock();
					is_empty = out_images.empty();
					out_imagesLock.unlock();
					if( is_empty )
					{
						break;
					}
				}
				else
				{
					out_imagesLock.lock();
					is_empty = out_images.empty();
					out_imagesLock.unlock();
				}
				
				if(is_empty)
				{
					this_thread::sleep_for(chrono::milliseconds(1));
				}
				else
				{
					out_imagesLock.lock();
					auto image_line = out_images.front();
					out_images.pop();
					out_imagesLock.unlock();

					*outfile << image_line << "\n";
					file_count++;
					if((file_count&0xFF)==0)
					{
						outfile->flush();
						std::cout << "Write File Count: " << file_count << std::endl;
					}
				}
			}
			
		});

		/* Release thread resources. */
		for (auto &w : workers) {
			if (w.joinable()) w.join();
		}
		break_out_worker = true;
		if (out_worker.joinable()) out_worker.join();
	}
	auto end_time = GetClockTicks();
	std::cout << "Use Time:" << (float)(end_time - start_time) / 1000.0f << "s" << std::endl;

	if(nullptr != outfile)
	{
		outfile->close();
		delete outfile;
		outfile = nullptr;
	}

	// getchar();
    return 0;
}

